﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Irony.Parsing;

namespace DPLParser.DPLNodeParser
{
    public static class ParseTreeNodeParsingExtensions
    {

#region NODE COMPARISON FUNCTIONS
        /// <summary>
        /// Compare 2 ParseTreeNode child by child
        /// </summary>
        /// <param name="currentParseNode"></param>
        /// <param name="comparedNode"></param>
        /// <returns></returns>
        public static bool Equals(this ParseTreeNode currentParseNode, ParseTreeNode comparedNode)
        {
            if (currentParseNode.Term.Name == comparedNode.Term.Name)
            {
                if (currentParseNode.ChildNodes.Count == comparedNode.ChildNodes.Count)
                {
                    for (int counter = 0; counter < currentParseNode.ChildNodes.Count; counter++)
                    {
                        bool isChildEqual = currentParseNode.ChildNodes[counter].Equals(comparedNode.ChildNodes[counter]);
                        if (!isChildEqual)
                            return false;
                    }
                }
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// Find a comparedNode in the tree of children of the current currentParseNode
        /// </summary>
        /// <param name="currentParseNode"></param>
        /// <param name="comparedNode"></param>
        /// <returns></returns>
        public static bool Contains(this ParseTreeNode currentParseNode, ParseTreeNode comparedNode)
        {
            if (currentParseNode.Equals(comparedNode))
                return true;
            else
            {
                foreach(var aChildNode in currentParseNode.ChildNodes)
                {
                    if (aChildNode.Contains(comparedNode))
                        return true;
                }
            }
            return false;
        }
#endregion

        #region DEBUGGING HELPERS
        private static List<string> DebuggingTreeNodeTerms; 

        private static void DebuggingHelper(string strNodeNameForDebug)
        {
            if (DebuggingTreeNodeTerms != null)
            {
                if (DebuggingTreeNodeTerms.Contains(strNodeNameForDebug))
                    Console.WriteLine("Inspecting node " + strNodeNameForDebug);
            }
        }




        public static ParseTreeNodeList GetNodesWithDebugging(this ParseTreeNode currentParseNode, string strQueryPath)
        {
            DebuggingTreeNodeTerms = new List<string>(strQueryPath.Split(new char[] { '.' }));
            return currentParseNode.GetNodes(strQueryPath);
        }

        #endregion
        
        public static ParseTreeNodeList GetAllNodesRecursively(this  ParseTreeNode currentParseNode, string strQueryPath, bool takeIntermediateSiblings)
        {
            if (currentParseNode == null)
                return new ParseTreeNodeList();
            ParseTreeNodeList returnNodes = currentParseNode.GetNodes(strQueryPath);
            bool foundChildrenSiblings = false;
            ParseTreeNodeList returnChildrenNodes = new ParseTreeNodeList();
            foreach(ParseTreeNode aChildNode in currentParseNode.ChildNodes)
            {
                ParseTreeNodeList childrenSiblings = aChildNode.GetAllNodesRecursively(strQueryPath,
                                                                                       takeIntermediateSiblings);
                if (childrenSiblings.Count > 0)
                    foundChildrenSiblings = true;

                returnChildrenNodes.AddRange(childrenSiblings);
            }
            if (!takeIntermediateSiblings)
            {
                // Do not return parent nodes if some children nodes 
                // have been found
                if (foundChildrenSiblings)
                    return returnChildrenNodes;
                else
                    return returnNodes;
            }
            else
            {
                returnNodes.AddRange(returnChildrenNodes);
            }
            return returnNodes;
        }

        private static string BuildPath(Queue<string> QueryItems)
        {
            string strReturnPath = "";
            while(QueryItems.Count>0)
            {
                var aQueryPathItem = QueryItems.Dequeue();
                strReturnPath = strReturnPath + "." + aQueryPathItem;
            }
            return strReturnPath;
        }

       

      
        public static ParseTreeNode GetNode(this ParseTreeNode currentParseNode, string strQueryPath)
        {
            ParseTreeNodeList foundNodes = currentParseNode.GetNodes(strQueryPath);
            if (foundNodes.Count > 1)
            {
                throw new ApplicationException("Error: several nodes matched the condition " + strQueryPath);
            }
            else if (foundNodes.Count == 0)
                return null;
            return foundNodes[0];
        }


        public static ParseTreeNodeList GetNodes(this ParseTreeNodeList currentParseNodeList, string strQueryPath)
        {
            ParseTreeNodeList currentList = new ParseTreeNodeList();
            foreach (ParseTreeNode aTreeNode in currentParseNodeList)
            {
                Queue<string> QueryElements = new Queue<string>(strQueryPath.Split(new char[] { '.' }));
                currentList.AddRange(aTreeNode.GetNodes(QueryElements));
            }

            return currentList;
        }

        public static ParseTreeNodeList GetNodes(this ParseTreeNode currentParseNode, string strQueryPath)
        {
            ParseTreeNodeList currentList = new ParseTreeNodeList();
            Queue<string> QueryElements = new Queue<string>(strQueryPath.Split(new char[] { '.' }));
            return currentParseNode.GetNodes(QueryElements);
        }

       
        private static ParseTreeNodeList GetNodesWithoutChangingQueryElements(this ParseTreeNode currentParseNode, Queue<string> QueryElements)
        {
            DebuggingHelper(currentParseNode.Term.Name);
            return currentParseNode.GetNodes(new Queue<string>(QueryElements.ToArray()));
        }

        /// <summary>
        /// Parse the Tree to find nodes defined in a Query queue.
        /// </summary>
        /// <param name="QueryElements">A queue of query items. The queue can be defined as ItemA.ItemB.ItemC for
        /// direct dependencies, or A..B for a dependency in any depth level in the tree (take the first match)
        /// A.B. query path will get the children of the found B TreeNode
        /// A.*.B will take the deeper matches in the tree found (skipping all intermediary B nodes).
        /// </param>
        /// <param name="currentParseNode"></param>
        /// <returns></returns>
        private static ParseTreeNodeList GetNodes(this ParseTreeNode currentParseNode, Queue<string> QueryElements)
        {
            ParseTreeNodeList returnList = new ParseTreeNodeList();

            // CASE 1: The Query queue is empty, returns no element.
            if ((QueryElements.Count == 0) || (currentParseNode == null))
                return returnList;

            string strCurrentQueryElement = QueryElements.Peek();

            if (strCurrentQueryElement == "**")
            {
                Queue<string> copiedElements = new Queue<string>(QueryElements.ToArray());
                copiedElements.Dequeue();
                ParseTreeNodeList recursiveSearchList = currentParseNode.GetAllNodesRecursively(BuildPath(copiedElements), true);
                returnList.AddRange(recursiveSearchList);
                return returnList;
            }
            else if (strCurrentQueryElement == "*")
            {
                Queue<string> copiedElements = new Queue<string>(QueryElements.ToArray());
                copiedElements.Dequeue();
                ParseTreeNodeList recursiveSearchList = currentParseNode.GetAllNodesRecursively(BuildPath(copiedElements), false);
                returnList.AddRange(recursiveSearchList);
                return returnList;
            }

            if (QueryElements.Count == 1)
            {
                // CASE 2: The current nodes match the Queue, which has a unique item
                if (currentParseNode.Term.Name == strCurrentQueryElement)
                {
                    QueryElements.Dequeue();
                    returnList.Add(currentParseNode);
                    return returnList;
                }
                else if (strCurrentQueryElement == "")
                {
                    // CASE3: A.B. query (get the children of a found B TreeNode)
                    // We are already inspecting the child of B
                    returnList.Add(currentParseNode);
                    return returnList;
                }
            }

            ParseTreeNodeList childNodeFoundElements = new ParseTreeNodeList();
            

            if (strCurrentQueryElement!="")
            {
                // CASE4: Search for a direct child element of the currentParseNode.
                if (currentParseNode.Term.Name == strCurrentQueryElement)
                {
                    Queue<string> CopiedQueryElements = new Queue<string>(QueryElements.ToArray());
                    CopiedQueryElements.Dequeue();
                    foreach (ParseTreeNode aChildNode in currentParseNode.ChildNodes)
                    {
                        childNodeFoundElements = aChildNode.GetNodesWithoutChangingQueryElements(CopiedQueryElements);
                        returnList.AddRange(childNodeFoundElements);
                    }
                }
            }
            else
            {
                // CASE5: Search for an indirect child element of the currentParseNode
                Queue<string> DoubleDequeuePurposedCopy = new Queue<string>(QueryElements.ToArray());
                Queue<string> DoubleDequeuePurposedCopy2 = new Queue<string>(QueryElements.ToArray());
                DoubleDequeuePurposedCopy.Dequeue();
                DoubleDequeuePurposedCopy2.Dequeue();
                if (DoubleDequeuePurposedCopy2.Count > 0)
                    DoubleDequeuePurposedCopy2.Dequeue();
                string strNextQueryElement = DoubleDequeuePurposedCopy.Peek();

                if (currentParseNode.Term.Name == strNextQueryElement)
                    returnList.Add(currentParseNode);
                else if ((strNextQueryElement == "") && (DoubleDequeuePurposedCopy2.Count>0) && (DoubleDequeuePurposedCopy2.Peek() == currentParseNode.Term.Name))
                {
                    // CASE 5.1: We have a ..Type querty with the current Node = Type
                    returnList.Add(currentParseNode);
                }
                else
                {
                    foreach (ParseTreeNode aChildNode in currentParseNode.ChildNodes)
                    {
                        if (strNextQueryElement == aChildNode.Term.Name)
                        {
                            childNodeFoundElements = aChildNode.GetNodesWithoutChangingQueryElements(DoubleDequeuePurposedCopy);
                        }
                        else
                        {
                            childNodeFoundElements = aChildNode.GetNodesWithoutChangingQueryElements(QueryElements);
                        }
                        returnList.AddRange(childNodeFoundElements);
                    }
                }
            }
            return returnList;
        }

        public static List<string> GetNodesContent(ParseTreeNodeList ParsedTreeNodes)
        {
            List<string> nodesContent = new List<string>();
            foreach(ParseTreeNode aTreeNode in ParsedTreeNodes)
            {
                // Get the current node content. 
                if (aTreeNode.Token != null)
                {
                    string strNodeContent = aTreeNode.Token.Text;
                    nodesContent.Add(strNodeContent);
                }
            }
            return nodesContent;
        }
    }
}
